using System;
using System.IO;
using System.Collections;
using System.Runtime.InteropServices;

namespace Sio.Mdm.Utils
{
	/// <summary>
	/// Wraps the API functions, structures and constants.
	/// </summary>
	internal class Kernel32 
	{
		public const char StreamSeparator = ':';
		public const int InvalidHandleError = -1;
		public const int MaxPath = 256;
		
		[Flags()] 
        public enum FileFlags : uint
		{
			WriteThrough = 0x80000000,
			Overlapped = 0x40000000,
			NoBuffering = 0x20000000,
			RandomAccess = 0x10000000,
			SequentialScan = 0x8000000,
			DeleteOnClose = 0x4000000,
			BackupSemantics = 0x2000000,
			PosixSemantics = 0x1000000,
			OpenReparsePoint = 0x200000,
			OpenNoRecall = 0x100000
		}

		[Flags()] 
        public enum FileAccessAPI : uint
		{
			GENERIC_READ = 0x80000000,
			GENERIC_WRITE = 0x40000000
		}
		/// <summary>
		/// Provides a mapping between a System.IO.FileAccess value and a FileAccessAPI value.
		/// </summary>
		/// <param name="Access">The <see cref="System.IO.FileAccess"/> value to map.</param>
		/// <returns>The <see cref="FileAccessAPI"/> value.</returns>
		public static FileAccessAPI Access2API(FileAccess Access) 
		{
			FileAccessAPI lRet = 0;
			if ((Access & FileAccess.Read)==FileAccess.Read) lRet |= FileAccessAPI.GENERIC_READ;
			if ((Access & FileAccess.Write)==FileAccess.Write) lRet |= FileAccessAPI.GENERIC_WRITE;
			return lRet;
		}

		[StructLayout(LayoutKind.Sequential)] 
        public struct LARGE_INTEGER 
		{
			public int Low;
			public int High;

			public long ToInt64() 
			{
				return (long)High * 4294967296 + (long)Low;
			}
		}

		[StructLayout(LayoutKind.Sequential)] 
        public struct WIN32_STREAM_ID 
		{
			public int dwStreamID;
			public int dwStreamAttributes;
			public LARGE_INTEGER Size;
			public int dwStreamNameSize;
		}
		
		[DllImport("kernel32")] public static extern IntPtr CreateFile(string Name, FileAccessAPI Access, FileShare Share, int Security, FileMode Creation, FileFlags Flags, int Template);
		[DllImport("kernel32")] public static extern bool DeleteFile(string Name);
		[DllImport("kernel32")] public static extern bool CloseHandle(IntPtr hObject);

		[DllImport("kernel32")] public static extern bool BackupRead(IntPtr hFile, IntPtr pBuffer, int lBytes, ref int lRead, bool bAbort, bool bSecurity, ref int Context);
		[DllImport("kernel32")] public static extern bool BackupRead(IntPtr hFile, ref WIN32_STREAM_ID pBuffer, int lBytes, ref int lRead, bool bAbort, bool bSecurity, ref int Context);
		[DllImport("kernel32")] public static extern bool BackupSeek(IntPtr hFile, int dwLowBytesToSeek, int dwHighBytesToSeek, ref int dwLow, ref int dwHigh, ref int Context);
	}

	/// <summary>
	/// Encapsulates a single alternative data stream for a file.
	/// </summary>
	public class StreamInfo 
	{
        #region Fields
        private FileStreams Parent; 
        #endregion

        #region Construction
        internal StreamInfo(FileStreams parent, string name, long size)
        {
            Parent = parent;
            _name = Name;
            _size = Size;
        } 
        #endregion

        #region Properties

        #region Public

        #region Name
        private string _name;
        /// <summary>
        /// The name of the stream.
        /// </summary>
        public string Name
        {
            get 
            { 
                return _name; 
            }
        } 
        #endregion

        #region Size
        private long _size;
        /// <summary>
        /// The size (in bytes) of the stream.
        /// </summary>
        public long Size
        {
            get 
            { 
                return _size; 
            }
        }  
        #endregion

        #endregion 

        #endregion

        #region Methods

        #region Public

        #region ToString
        /// <summary>
        /// Overridne to show human readable representation of object
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return String.Format("{1}{0}{2}{0}$DATA", Kernel32.StreamSeparator, Parent.FileName, _name);
        }
        #endregion

        #region Equals
        /// <summary>
        /// Overriden to check equality
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        public override bool Equals(Object o)
        {
            if (o is StreamInfo)
            {
                StreamInfo f = (StreamInfo)o;
                return (f._name.Equals(_name) && f.Parent.Equals(Parent));
            }
            else if (o is string)
            {
                return ((string)o).Equals(ToString());
            }
            else
                return base.Equals(o);
        }
        #endregion

        #region GetHashCode
        /// <summary>
        /// Overriden because og equality operator
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return ToString().GetHashCode();
        }
        #endregion

        #region Open
        /// <summary>
        /// Opens or creates the stream in read-write mode, with no sharing.
        /// </summary>
        /// <returns>A <see cref="System.IO.FileStream"/> wrapper for the stream.</returns>
        public FileStream Open()
        {
            return Open(FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
        }
        /// <summary>
        /// Opens or creates the stream in read-write mode with no sharing.
        /// </summary>
        /// <param name="mode">The <see cref="System.IO.FileMode"/> action for the stream.</param>
        /// <returns>A <see cref="System.IO.FileStream"/> wrapper for the stream.</returns>
        public FileStream Open(FileMode mode)
        {
            return Open(mode, FileAccess.ReadWrite, FileShare.None);
        }
        /// <summary>
        /// Opens or creates the stream with no sharing.
        /// </summary>
        /// <param name="mode">The <see cref="System.IO.FileMode"/> action for the stream.</param>
        /// <param name="access">The <see cref="System.IO.FileAccess"/> level for the stream.</param>
        /// <returns>A <see cref="System.IO.FileStream"/> wrapper for the stream.</returns>
        public FileStream Open(FileMode mode, FileAccess access)
        {
            return Open(mode, access, FileShare.None);
        }
        /// <summary>
        /// Opens or creates the stream.
        /// </summary>
        /// <param name="mode">The <see cref="System.IO.FileMode"/> action for the stream.</param>
        /// <param name="access">The <see cref="System.IO.FileAccess"/> level for the stream.</param>
        /// <param name="share">The <see cref="System.IO.FileShare"/> level for the stream.</param>
        /// <returns>A <see cref="System.IO.FileStream"/> wrapper for the stream.</returns>
        public FileStream Open(FileMode mode, FileAccess access, FileShare share)
        {
            try
            {
                IntPtr hFile = Kernel32.CreateFile(ToString(), Kernel32.Access2API(access), share, 0, mode, 0, 0);
                Microsoft.Win32.SafeHandles.SafeFileHandle safeHandle = new Microsoft.Win32.SafeHandles.SafeFileHandle(hFile, true);
                // TODO: What is with share !!!!
                return new FileStream(safeHandle, access);
            }
            catch
            {
                return null;
            }
        }
        #endregion

        #region Delete
        /// <summary>
        /// Deletes the stream from the file.
        /// </summary>
        /// <returns>A <see cref="System.Boolean"/> value: true if the stream was deleted, false if there was an error.</returns>
        public bool Delete()
        {
            return Kernel32.DeleteFile(ToString());
        }
        #endregion

        #endregion

        #endregion
	}


	/// <summary>
	/// Encapsulates the collection of alternative data streams for a file.
	/// A collection of <see cref="StreamInfo"/> objects.
	/// </summary>
	public class FileStreams : CollectionBase 
	{
        #region Construction
        /// <summary>
        /// Construct stream for passed file
        /// </summary>
        /// <param name="file">Path to file stream will be bound to</param>
		public FileStreams(string file) 
		{
			File = new FileInfo(file);
			InitStreams();
		}
        /// <summary>
        /// Construct stream for passed file
        /// </summary>
        /// <param name="file">Information on file to use stream with</param>
		public FileStreams(FileInfo file) 
		{
			File = file;
			InitStreams();
		}
        #endregion

        #region Properties

        #region Public

        #region File
        private FileInfo File;
        /// <summary>
        /// Returns the <see cref="System.IO.FileInfo"/> object for the wrapped file. 
        /// </summary>
        public FileInfo FileInfo
        {
            get
            {
                return File;
            }
        }
        #endregion

        #region FileName
        /// <summary>
        /// Returns the full path to the wrapped file.
        /// </summary>
        public string FileName
        {
            get
            {
                return File.FullName;
            }
        }
        #endregion

        #region FileSize
        /// <summary>
        /// Returns the size of the main data stream, in bytes.
        /// </summary>
        public long FileSize
        {
            get
            {
                return File.Length;
            }
        }
        #endregion

        #region Size
        /// <summary>
        /// Returns the size of all streams for the file, in bytes.
        /// </summary>
        public long Size
        {
            get
            {
                long size = this.FileSize;

                foreach (StreamInfo s in this)
                {
                    size += s.Size;
                }

                return size;
            }
        }
        #endregion

        #endregion 
        #endregion

        #region Methods

        #region Public

        #region Open
        /// <summary>
        /// Opens or creates the default file stream.
        /// </summary>
        /// <returns><see cref="System.IO.FileStream"/></returns>
        public FileStream Open()
        {
            return new FileStream(File.FullName, FileMode.OpenOrCreate);
        }

        /// <summary>
        /// Opens or creates the default file stream.
        /// </summary>
        /// <param name="mode">The <see cref="System.IO.FileMode"/> for the stream.</param>
        /// <returns><see cref="System.IO.FileStream"/></returns>
        public FileStream Open(FileMode mode)
        {
            return new FileStream(File.FullName, mode);
        }

        /// <summary>
        /// Opens or creates the default file stream.
        /// </summary>
        /// <param name="mode">The <see cref="System.IO.FileMode"/> for the stream.</param>
        /// <param name="access">The <see cref="System.IO.FileAccess"/> for the stream.</param>
        /// <returns><see cref="System.IO.FileStream"/></returns>
        public FileStream Open(FileMode mode, FileAccess access)
        {
            return new FileStream(File.FullName, mode, access);
        }

        /// <summary>
        /// Opens or creates the default file stream.
        /// </summary>
        /// <param name="mode">The <see cref="System.IO.FileMode"/> for the stream.</param>
        /// <param name="access">The <see cref="System.IO.FileAccess"/> for the stream.</param>
        /// <param name="share">The <see cref="System.IO.FileShare"/> for the stream.</param>
        /// <returns><see cref="System.IO.FileStream"/></returns>
        public FileStream Open(FileMode mode, FileAccess access, FileShare share)
        {
            return new FileStream(File.FullName, mode, access, share);
        }
        #endregion

        #region Delete
        /// <summary>
        /// Deletes the file, and all alternative streams.
        /// </summary>
        public void Delete()
        {
            for (int i = base.List.Count; i > 0; i--)
            {
                base.List.RemoveAt(i);
            }
            File.Delete();
        }
        #endregion

        #region Collection operations
        /// <summary>
        /// Add an alternative data stream to this file.
        /// </summary>
        /// <param name="name">The name for the stream.</param>
        /// <returns>The index of the new item.</returns>
        public int Add(string name)
        {
            StreamInfo FSI = new StreamInfo(this, name, 0);
            int i = base.List.IndexOf(FSI);
            if (i == -1) i = base.List.Add(FSI);
            return i;
        }
        /// <summary>
        /// Removes the alternative data stream with the specified name.
        /// </summary>
        /// <param name="name">The name of the string to remove.</param>
        public void Remove(string name)
        {
            StreamInfo FSI = new StreamInfo(this, name, 0);
            int i = base.List.IndexOf(FSI);
            if (i > -1) base.List.RemoveAt(i);
        }

        /// <summary>
        /// Returns the index of the specified <see cref="StreamInfo"/> object in the collection.
        /// </summary>
        /// <param name="stream">The object to find.</param>
        /// <returns>The index of the object, or -1.</returns>
        public int IndexOf(StreamInfo stream)
        {
            return base.List.IndexOf(stream);
        }
        /// <summary>
        /// Returns the index of the <see cref="StreamInfo"/> object with the specified name in the collection.
        /// </summary>
        /// <param name="name">The name of the stream to find.</param>
        /// <returns>The index of the stream, or -1.</returns>
        public int IndexOf(string name)
        {
            return base.List.IndexOf(new StreamInfo(this, name, 0));
        }

        /// <summary>
        /// Get strema by sequental index
        /// </summary>
        /// <param name="index">Index of stream to return</param>
        /// <returns></returns>
        public StreamInfo this[int index]
        {
            get { return (StreamInfo)base.List[index]; }
        }
        /// <summary>
        /// Get index by it's name
        /// </summary>
        /// <param name="name">Name of stream to return</param>
        /// <returns></returns>
        public StreamInfo this[string name]
        {
            get
            {
                int i = IndexOf(name);
                if (i == -1) return null;
                else return (StreamInfo)base.List[i];
            }
        }

        /// <summary>
        /// Get enumerator for traversing file streams
        /// </summary>
        /// <returns></returns>
        public new StreamEnumerator GetEnumerator()
        {
            return new StreamEnumerator(this);
        }
        #endregion

        #endregion

        #region Protected
        /// <summary>
        /// Throws an exception if you try to add anything other than a StreamInfo object to the collection.
        /// </summary>
        protected override void OnInsert(int index, object value)
        {
            if (!(value is StreamInfo)) throw new InvalidCastException();
        }
        /// <summary>
        /// Throws an exception if you try to add anything other than a StreamInfo object to the collection.
        /// </summary>
        protected override void OnSet(int index, object oldValue, object newValue)
        {
            if (!(newValue is StreamInfo)) throw new InvalidCastException();
        }

        /// <summary>
        /// Deletes the stream from the file when you remove it from the list.
        /// </summary>
        protected override void OnRemoveComplete(int index, object value)
        {
            try
            {
                StreamInfo FSI = (StreamInfo)value;
                if (FSI != null) FSI.Delete();
            }
            catch { }
        }
        #endregion

        #region Private

        #region InitStreams
        /// <summary>
        /// Reads the streams from the file.
        /// </summary>
        private void InitStreams()
        {
            //Open the file with backup semantics
            IntPtr hFile = Kernel32.CreateFile(File.FullName, Kernel32.FileAccessAPI.GENERIC_READ, FileShare.Read, 0, FileMode.Open, Kernel32.FileFlags.BackupSemantics, 0);
            if (hFile.ToInt32() == Kernel32.InvalidHandleError) return;

            try
            {
                Kernel32.WIN32_STREAM_ID sid = new Kernel32.WIN32_STREAM_ID();
                int dwStreamHeaderSize = Marshal.SizeOf(sid);
                int Context = 0;
                bool Continue = true;
                while (Continue)
                {
                    //Read the next stream header
                    int lRead = 0;
                    Continue = Kernel32.BackupRead(hFile, ref sid, dwStreamHeaderSize, ref lRead, false, false, ref Context);
                    if (Continue && lRead == dwStreamHeaderSize)
                    {
                        if (sid.dwStreamNameSize > 0)
                        {
                            //Read the stream name
                            lRead = 0;
                            IntPtr pName = Marshal.AllocHGlobal(sid.dwStreamNameSize);
                            try
                            {
                                Continue = Kernel32.BackupRead(hFile, pName, sid.dwStreamNameSize, ref lRead, false, false, ref Context);
                                char[] bName = new char[sid.dwStreamNameSize];
                                Marshal.Copy(pName, bName, 0, sid.dwStreamNameSize);

                                //Name is of the format ":NAME:$DATA\0"
                                string sName = new string(bName);
                                int i = sName.IndexOf(Kernel32.StreamSeparator, 1);
                                if (i > -1) sName = sName.Substring(1, i - 1);
                                else
                                {
                                    //This should never happen. 
                                    //Truncate the name at the first null char.
                                    i = sName.IndexOf('\0');
                                    if (i > -1) sName = sName.Substring(1, i - 1);
                                }

                                //Add the stream to the collection
                                base.List.Add(new StreamInfo(this, sName, sid.Size.ToInt64()));
                            }
                            finally
                            {
                                Marshal.FreeHGlobal(pName);
                            }
                        }

                        //Skip the stream contents
                        int l = 0; int h = 0;
                        Continue = Kernel32.BackupSeek(hFile, sid.Size.Low, sid.Size.High, ref l, ref h, ref Context);
                    }
                    else break;
                }
            }
            finally
            {
                Kernel32.CloseHandle(hFile);
            }
        }
        #endregion

        #endregion 

        #endregion

        #region Classes
        /// <summary>
        /// Implementation of file stream enumerator
        /// </summary>
		public class StreamEnumerator : object, IEnumerator 
		{
			private IEnumerator baseEnumerator;
            
            /// <summary>
            /// COnstruct enumerator
            /// </summary>
            /// <param name="mappings"></param>
			public StreamEnumerator(FileStreams mappings) 
			{
				this.baseEnumerator = ((IEnumerable)(mappings)).GetEnumerator();
			}
            
            /// <summary>
            /// Get current enumerator position
            /// </summary>
			public StreamInfo Current 
			{
				get 
				{
					return ((StreamInfo)(baseEnumerator.Current));
				}
			}
            
			object IEnumerator.Current 
			{
				get 
				{
					return baseEnumerator.Current;
				}
			}
            
            /// <summary>
            /// Move to nex record
            /// </summary>
            /// <returns></returns>
			public bool MoveNext() 
			{
				return baseEnumerator.MoveNext();
			}
            
			bool IEnumerator.MoveNext() 
			{
				return baseEnumerator.MoveNext();
			}
            
            /// <summary>
            /// Reset enumerator to initial position
            /// </summary>
			public void Reset() 
			{
				baseEnumerator.Reset();
			}
            
			void IEnumerator.Reset() 
			{
				baseEnumerator.Reset();
			}
		}
        #endregion
	}
}